
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sstream>
#include "MemCache.h"
#include "define.h"
#include "Debug.h"
#include "Helper.h"
#include "dlfacade.h"
 
using namespace std;
Helper * Helper::_instance = NULL;

Helper::Helper() 
{
	m_pCache = NULL;
	m_pStorage = NULL;
}

Helper::~Helper()
{
	if( m_pCache != NULL)
		delete m_pCache;
 
        DlFacade::Release(true);
        m_pStorage = NULL;
}

Helper * Helper::Instance(const char *confFile)
{
	if (NULL == _instance) 
	{
		_instance = new Helper();
		if ( !_instance->Startup(confFile) ) {
			delete _instance; 
			_instance = NULL;
		}
	}
	return _instance;
}

void Helper::Release(bool bForce)
{
	if (_instance) 
	{
		if(bForce)
		{
			delete _instance; 
			_instance = NULL;
		}
	}
}

bool  Helper::Startup(const char *confFile)
{
	if(confFile != NULL)
		m_sConfFile  = confFile;
	if(m_sConfFile.length() < 1)
	{
		ERRO<<": no helper.conf be given!"<<endl;
		return false;
	}
	struct stat st;
	if (lstat(confFile, &st) != 0)
	{
		ERRO<<":"<<confFile<<" is not exist!"<<endl;
		return false;
	}

	GKeyFile *kv = g_key_file_new();
	GError *err = NULL;
	if(TRUE != g_key_file_load_from_file(kv,confFile, G_KEY_FILE_NONE , &err))
	{
		if (err != NULL)
		{
			ERRO << "Unable to read file:" << err->message << endl;
			g_error_free (err);
		}
		g_key_file_free(kv);
		return false;
	}

	char *pval =  g_key_file_get_value(kv,"global", "ip_acl_config",  NULL);
	if(pval == NULL)
        { 
                m_sIPAcl = "";
		ERRO <<": no ip_acl_config setting "<< endl;
        } 
	else
	{

		m_sIPAcl  = pval;
                g_free(pval);
	}

	pval =  g_key_file_get_value(kv,"global", "cache_config",  NULL);
	if(pval == NULL)
        {
                m_sCache_Cfg = "";
		ERRO <<": no cache_config setting, so system will can't use cache function"<< endl;
        }
	else
	{

		m_sCache_Cfg  = pval;
                g_free(pval);
	}

	pval =  g_key_file_get_value(kv,"global", "storage_config",  NULL);
	if(pval == NULL)
        {
                m_sStorage_Cfg = "";
		ERRO <<":no storage_config setting, so system will can't use Storage function"<< endl;
        } 
	else
	{

		m_sStorage_Cfg  = pval;
		g_free(pval);
	}

	pval =  g_key_file_get_value(kv,"global", "storage",  NULL);
	if(pval == NULL)
		ERRO <<":no storage setting "<< endl;
	else
	{
		
                DlFacade *dlp = DlFacade::Instance((char *)pval, m_sStorage_Cfg.c_str()); 
                if(dlp != NULL)
                {
                     m_pStorage = dlp->GetStorage();
                }
		g_free(pval);
	}

	pval =  g_key_file_get_value(kv,"global", "port",  NULL);
	if(pval == NULL)
		m_iPort = 4080;
	else
	{
		m_iPort = atoi((char *) pval);
		g_free(pval);
	}
        INFO<<"listen port = "<< m_iPort <<endl;
	pval =  g_key_file_get_value(kv,"global", "wport",  NULL);
	if(pval == NULL)
		m_iwPort = 9443;
	else
	{
		m_iwPort = atoi((char *) pval);
		g_free(pval);
	}

	pval =  g_key_file_get_value(kv,"global", "threads",  NULL);
	if(pval == NULL)
		m_iThreads = 10;
	else
	{
		m_iThreads = atoi((char *) pval);
		g_free(pval);
	}

        g_key_file_free(kv);
	DEBUG1(" MemCache ");
	if(m_sCache_Cfg.length() > 0)
	{ 
		m_pCache = new MemCache();
		bool bret = m_pCache->Initialize(m_sCache_Cfg.c_str());
		if(!bret)
		{
			ERRO <<"return = false: can't Initialize Cache Instance"<<endl;
			delete m_pCache;
			m_pCache = NULL;
			return false;
		}
	}
        else
            INFO<<"No CACHE SYSTEM to USE !!!! "<<endl;
        if(m_sCache_Cfg.length() <=0 && m_sStorage_Cfg.length() <= 0)
        {
               ERRO <<"No Cache and No Storage,I Don't Know what I can do,so SHUTDOWN !!!!! "<<endl;
               return false;
        }
 
	return true;
}

int Helper :: ResetCache(const char *ip, int port)
{
	DEBUG3(ip, " : ", port);
	if(m_pCache != NULL)
		return m_pCache->ResetCacheStatus(ip, port);
	return PANDA_SUCCESS;

}

int Helper :: get_mem_return_info(char **wbuf, int retcode,unsigned &wsize, unsigned &wbytes)
{
	wbytes = 2*sizeof(int);
	DEBUG4("wsize=", wsize, "; wbytes=", wbytes );
	if(wsize < wbytes)
	{
DEBUG1("realloc(*wbuf, wbytes+1)");
		*wbuf = (char *)g_realloc(*wbuf, wbytes +1);
		if(*wbuf == NULL){
			ERRO<<"can't malloc for write buffer !"<<endl;
			wsize = wbytes = 0;
			return PANDA_ERR_SYSTEM;
		}
		wsize = wbytes + 1;
	}

        unsigned usedlen = 0;
DEBUG4("wbuf =", *wbuf,"; bcopy wbytes = ", wbytes);
        bcopy(&wbytes, *wbuf + usedlen, sizeof(unsigned int));            //str_length
        usedlen += sizeof(unsigned int);
DEBUG2("bcopy retcode = ", retcode);
        bcopy(&retcode, *wbuf + usedlen, sizeof(int));   // retcode
        
        return 0;
}

int Helper::write(const char *data, unsigned dlen)
{
	unsigned uint_len = sizeof(uint32_t);
	if( dlen <= uint_len*5 )
	{
		DEBUG4("dlen <= uint_len*5 is true : dlen =", dlen, "; uint_len*5=", uint_len*5);
		return PANDA_ERR_INPUT_DATA;
	}
	int iret = PANDA_SUCCESS;
	if(m_pCache != NULL)
		iret = m_pCache->Set(data, dlen);
	if(m_pStorage != NULL)
	{
		unsigned ilen = 0;
		int ihash = 0;
		bcopy(data, &ihash, uint_len);
		ilen += uint_len;
		DEBUG4("ihash = ", ihash, "; offset = ", ilen);
		//   long expire = 0;
		//   bcopy(data + ilen, &expire, sizeof(long));
		ilen += sizeof(long);
		//   DEBUG4("expire = ", expire, "; offset = ", ilen + 4 - sizeof(long));
		int argc = 0;
		bcopy(data + ilen , &argc, uint_len);
		ilen += uint_len;
		DEBUG4("argc = ", argc, "; offset = ", ilen);
		if( argc <= 0)
		{
			DEBUG2("argc=0, means no key=value to save! ilen =", ilen);
			return PANDA_ERR_INPUT_DATA;
		}
		int iret = PANDA_MC_FAIL;
		GHashTable *kv = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
		for(int i=0; i< argc; i++)
		{
			int klen ,vlen;
			bcopy(data + ilen, &klen, uint_len);
			ilen += uint_len;
			if(ilen + klen >= dlen || klen > PANDA_MC_KEY_MAX )
			{
				DEBUG4("ilen+klen>=dlen means no value; ilen=", ilen, "; klen=",klen);
				iret = PANDA_ERR_INPUT_DATA;
				break;
			}
			char *key = (char *)data + ilen;
			ilen += klen;
			bcopy(data + ilen, &vlen, uint_len);
			ilen += uint_len;
			if(ilen + vlen > dlen || klen > PANDA_MC_VALUE_MAX )
			{
				DEBUG4("ilen+vlen >dlen means value is error; ilen=", ilen, "; vlen=", vlen);
				iret = PANDA_ERR_INPUT_DATA;
				break;
			}
			char *val = (char *)data + ilen;
			ilen += vlen;
			g_hash_table_insert(kv, g_strndup(key, klen), val- uint_len);         
		}

		m_pStorage->Write(ihash , kv);
		g_hash_table_destroy(kv);
	}
	return iret;	
}

int Helper::read(const char *data, unsigned dlen, char **wbuf,unsigned &wsize, unsigned &wbytes)
{
	unsigned uint_len = sizeof(uint32_t);
	if( dlen <= uint_len*3 )
	{
		DEBUG4("dlen <= uint_len*5 is true : dlen =", dlen, "; uint_len*5=", uint_len*3);
		return PANDA_ERR_INPUT_DATA;
	}
	int iret = PANDA_SUCCESS;
	if(m_pCache != NULL)
		iret = m_pCache->Get(data, dlen, wbuf, wsize, wbytes);
	unsigned ilen = 0;
	/*
 * get item's counts from wbuf which return by Get, so can know if all be cached!
 */
	int argc = 0;
	if(wbytes >= 3* uint_len)
		bcopy(*wbuf + 2*uint_len, &argc, uint_len);
/*
 * get item's counts from input data which means user want to read
 */
	int rargc = 0;
	bcopy(data + uint_len, &rargc, uint_len);
	DEBUG4("mem_get argc = ", argc, "; read argc = ", rargc);
	if(m_pStorage != NULL && rargc > argc )
	{ // if some key is not be cached!
	    int ihash = 0;
	    bcopy(data, &ihash, uint_len);
	    ilen += uint_len;
	    DEBUG4("ihash = ", ihash, "; offset = ", ilen);
	    iret = PANDA_MC_FAIL;
	    GHashTable *kv = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
            ilen = 2*uint_len;
	    for(int i=0; i< rargc; i++)
	    { // get all key which user want to read
		    int klen ;
		    bcopy(data + ilen, &klen, uint_len);
		    ilen += uint_len;
		    if(ilen + klen > dlen || klen > PANDA_MC_KEY_MAX )
		    {
			    DEBUG6("ilen+klen>=dlen means no value; ilen=", ilen,"; dlen=", dlen, "; klen=",klen);
			    iret = PANDA_ERR_INPUT_DATA;
			    break;
		    }
		    char *key = (char *)data + ilen;
		    ilen += klen;
		    g_hash_table_insert(kv, g_strndup(key, klen), g_strdup(" "));         
	    }
            ilen = 3*uint_len;
	    for(int i=0; i< argc; i++)
	    {  // remove key which has been get from cache system!
		    int klen ;
		    bcopy(*wbuf + ilen, &klen, uint_len);
		    ilen += uint_len;
		    char *key = g_strndup(*wbuf + ilen, klen);
		    ilen += klen;
       	            bcopy(*wbuf + ilen, &klen, uint_len);
		    ilen += uint_len;
		    ilen += klen;
              
                    if(g_hash_table_lookup(kv, key) != NULL)
                       g_hash_table_remove(kv, key);
                    g_free(key);
	    }
            int iread = 0;
	    if(g_hash_table_size(kv) > 0)
	    {  // read from db, function Read will read, if value exist, it will be saved into kv, else will be removed key from kv;
		  iread = m_pStorage->Read(ihash, kv);
                  DEBUG4("iread=", iread, ";  size = ", g_hash_table_size(kv));
            }
/*
 * value is exist in db, so save it to cache
 */
	    if(g_hash_table_size(kv) > 0){
		    if(m_pCache != NULL)
			    m_pCache->Set(ihash, kv);
		    GHashTableIter iter;
		    gpointer keyit, valueit;
		    g_hash_table_iter_init (&iter, kv);
                    if(argc == 0)
                        ilen = 3*uint_len;
		    else ilen = wbytes;
                    if(ilen < 3*uint_len)
                         ilen = 3*uint_len;
                    DEBUG2("current usedlen = ", ilen);
		    //assert(wbytes > 3*uint_len);
		    unsigned ik = 0, iv = 0;
		    while (g_hash_table_iter_next (&iter, &keyit, &valueit))
		    {
			    ik = strlen((char *)keyit); 
			    iv = strlen((char *)valueit);
			    if(wsize < ilen + ik +iv+2*uint_len)
			    {
				    *wbuf = (char *)g_realloc(*wbuf, wsize+ilen+ik+iv+2*uint_len);
				    if(*wbuf == NULL){
					    ERRO<<"can't malloc for write buffer !"<<endl;
					    wsize = wbytes = 0;
                                            iret = PANDA_ERR_SYSTEM;
                                            break;
				    }
				    wsize = wsize + ilen + ik + iv + 2*uint_len;
			    }
                            bcopy(&ik, *wbuf + ilen, uint_len);
                            ilen += uint_len;
                            bcopy(keyit, *wbuf + ilen, ik);
                            ilen += ik;
                            bcopy(&iv, *wbuf + ilen, uint_len);
                            ilen += uint_len;
                            bcopy(valueit, *wbuf+ilen, iv);
                            ilen += iv;
                            argc++;
                            DEBUG6("item[", argc, "] =>", (char *)keyit," = ",(char *)valueit);
		    }
		    bcopy(&ilen, *wbuf, uint_len);
                    wbytes = ilen;
                    iret = PANDA_SUCCESS;
		    bcopy(&iret, *wbuf+uint_len, uint_len);
		    bcopy(&argc, *wbuf+2*uint_len, uint_len);
                    DEBUG2("wbytes = ", wbytes);
	    } 
	    else if( argc <= 0 )
	    {
                iret = PANDA_ERR_NO_DATA;
                DEBUG1("Can't read Data from DB!");
            }
	    g_hash_table_destroy(kv);
    }
    else
    {
        DEBUG1("m_pStorage == NULL !"); 
    }
    return iret;	
}

int Helper::incr(const char *data, unsigned dlen, char **wbuf,unsigned &wsize, unsigned &wbytes)
{
	int iret = PANDA_SUCCESS;
	if(m_pCache != NULL)
		iret = m_pCache->Incr(data, dlen, wbuf, wsize, wbytes);
	if(m_pStorage != NULL)
	{
	    unsigned uint_len = sizeof(uint32_t);
	    if( dlen < uint_len*3 +1 )
	    {
		    DEBUG4("dlen < uint_len*3+1: dlen =", dlen, "; uint_len*3=", uint_len*3);
		    return PANDA_ERR_INPUT_DATA;
	    }
	    unsigned ilen = 0;
	    int ihash = 0;
	    bcopy(data, &ihash, uint_len);
	    ilen += uint_len;
	    DEBUG4("ihash = ", ihash, "; offset = ", ilen);
	    int klen ;
	    bcopy(data + ilen, &klen, uint_len);
	    ilen += uint_len;
	    if(ilen + klen >= dlen || klen > PANDA_MC_KEY_MAX )
	    {
		    DEBUG4("ilen+klen>=dlen means no value; ilen=", ilen, "; klen=",klen);
		    return PANDA_ERR_INPUT_DATA;
	    }
	    char *key =  g_strndup((char *)data + ilen, klen);
	    ilen += klen;
	    unsigned int ival = 0;
	    bcopy(data +ilen , &ival, uint_len);
            int iival = 1* ival;
	    m_pStorage->Atom(ihash, key, iival, iret != 0);
	    if(iret != 0)
	    {
		    GHashTable *h = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
		    g_hash_table_insert(h, key, g_strdup_printf("%d", iival));
		    if(m_pCache != NULL)
			    m_pCache->Set(ihash, h);
                    int iretcode = 0;
                    bcopy(&iretcode, *wbuf + uint_len, uint_len);                    
                    int64_t iretval = (int64_t)iival;
                    bcopy(&iretval, *wbuf + 2*uint_len, sizeof(int64_t)); 
		    wbytes = 2*uint_len + sizeof(int64_t); 
		    bcopy(&wbytes, *wbuf, uint_len ); 
		    g_hash_table_destroy(h);
                    iret = PANDA_SUCCESS;
	    }
	    else
            {
                    DEBUG2("memcached_increment successful! wbytes=", wbytes ); 
		    g_free(key);
            } 
    }
    return iret;	
}

int Helper:: decr(const char *data, unsigned dlen, char **wbuf,unsigned &wsize, unsigned &wbytes)
{
	int iret = PANDA_SUCCESS;
	if(m_pCache != NULL)
		iret = m_pCache->Decr(data, dlen, wbuf, wsize, wbytes);
	if(m_pStorage != NULL)
	{
		unsigned uint_len = sizeof(uint32_t);
		if( dlen < uint_len*3+1 )
		{
			DEBUG4("dlen <= uint_len*3+1: dlen =", dlen, "; uint_len*3=", uint_len*3);
			return PANDA_ERR_INPUT_DATA;
		}
		unsigned ilen = 0;
		int ihash = 0;
		bcopy(data, &ihash, uint_len);
		ilen += uint_len;
		DEBUG4("ihash = ", ihash, "; offset = ", ilen);
		int klen ;
		bcopy(data + ilen, &klen, uint_len);
		ilen += uint_len;
		if(ilen + klen >= dlen || klen > PANDA_MC_KEY_MAX )
		{
			DEBUG4("ilen+klen>=dlen means no value; ilen=", ilen, "; klen=",klen);
			return PANDA_ERR_INPUT_DATA;
		}
		char *key =  g_strndup((char *)data + ilen, klen);
		ilen += klen;
		unsigned int ival = 0;
		bcopy(data +ilen , &ival, uint_len);
                int iival = -1*ival;
		m_pStorage->Atom(ihash, key, iival, iret !=0);
		if(iret != 0)
		{
			GHashTable *h = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
			g_hash_table_insert(h, key, g_strdup_printf("%d", iival));
			if(m_pCache != NULL)
				m_pCache->Set(ihash, h);
			int iretcode = 0;
			bcopy(&iretcode, *wbuf + uint_len, uint_len); 
			int64_t iretval = (int64_t)iival;
                        DEBUG2("result val = ", iretval);
			bcopy(&iretval, *wbuf + 2*uint_len, sizeof(int64_t)); 
			wbytes = 2*uint_len + sizeof(int64_t); 
			bcopy(&wbytes, *wbuf, uint_len ); 
			g_hash_table_destroy(h);
                        iret = PANDA_SUCCESS;
		}
		else
                {
                        DEBUG1("memcached_decrement successful");
			g_free(key);
                } 
	}
	return iret;	
}

int Helper:: remove(const char *data, unsigned dlen)
{
	int iret = PANDA_SUCCESS;
	if(m_pCache != NULL)
		m_pCache->Delete(data, dlen);
	if(m_pStorage != NULL)
	{
		unsigned uint_len = sizeof(uint32_t);
		if( dlen < uint_len*3 + 1 )
		{
			DEBUG4("dlen <= uint_len*3+1 : dlen =", dlen, "; uint_len*3=", uint_len*3);
			return PANDA_ERR_INPUT_DATA;
		}
		unsigned ilen = 0;
		int ihash = 0;
		bcopy(data, &ihash, uint_len);
		ilen += uint_len;
		DEBUG4("ihash = ", ihash, "; offset = ", ilen);
		int argc = 0;
		bcopy(data + ilen , &argc, uint_len);
		ilen += uint_len;
		DEBUG4("argc = ", argc, "; offset = ", ilen);
		if( argc <= 0)
		{
			DEBUG2("argc=0, means no key=value to save! ilen =", ilen);
			return PANDA_ERR_INPUT_DATA;
		}
                ilen = 2*uint_len;
		GHashTable *kv = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
		for(int i=0; i< argc; i++)
		{
			int klen ;
			bcopy(data + ilen, &klen, uint_len);
			ilen += uint_len;
                        DEBUG4("used = ", ilen, "; klen=", klen); 
			if(ilen + klen > dlen || klen > PANDA_MC_KEY_MAX )
			{
				DEBUG4("ilen+klen>=dlen; ilen=", ilen, "; klen=",klen);
				iret = PANDA_ERR_INPUT_DATA;
                                DEBUG4("argc = ", argc, "; idx = ", i);
				break;
			}
			char *key = (char *)data + ilen;
			ilen += klen;
			g_hash_table_insert(kv, g_strndup(key, klen), key - uint_len);  
		}
		m_pStorage->Delete(ihash, kv);
		g_hash_table_destroy(kv);
	}
	else { DEBUG1(" m_pStorage is NULL ");       }
	return iret;	
}

int Helper :: Do_Method(int method, char *data, unsigned dlen,char **wbuf, unsigned &wsize, unsigned &wbytes)
{
	wbytes = 0;
	int iret = PANDA_ERR_INPUT_DATA;
	DEBUG8("wbuf =", *wbuf, "; wsize=", wsize,"; wbytes=", wbytes,"; method=", method );

	switch(method)
	{ 
		case PANDA__SET:
			if(m_pCache != NULL)
				return m_pCache->Set(data, dlen);
			else
				break;
		case PANDA__GET:
			if(m_pCache != NULL)
				iret = m_pCache->Get(data, dlen, wbuf, wsize, wbytes);
			if(iret != 0)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		case PANDA__INCR:
			if(m_pCache != NULL)
				iret = m_pCache->Incr(data, dlen, wbuf, wsize, wbytes);
			if(iret != 0)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		case PANDA__DECR:
			if(m_pCache != NULL) 
				iret= m_pCache->Decr(data, dlen, wbuf, wsize, wbytes);
			if(iret != 0)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		case PANDA__DEL:
			if(m_pCache != NULL)
				return m_pCache->Delete(data, dlen);
			break;
			/////////////////////////////////////////////////////////////////////
			//          with storage !
			/////////////////////////////////////////////////////////////////////
		case PANDA_DB_SET:
			if(m_pCache != NULL || m_pStorage != NULL)
				return write(data, dlen);
			break;
		case PANDA_DB_GET:
			if(m_pCache != NULL || m_pStorage != NULL)
				iret = read(data, dlen, wbuf, wsize, wbytes);
			if(iret < 0)
			{ 
				DEBUG2("read's return = ", iret);
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			}
			break;
		case PANDA_DB_INCR:
			if(m_pCache != NULL || m_pStorage != NULL)
				iret = incr(data, dlen, wbuf, wsize, wbytes);
			if(iret != 0)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		case PANDA_DB_DECR: 
			if(m_pCache != NULL || m_pStorage != NULL)
				iret= decr(data, dlen, wbuf, wsize, wbytes);
			if(iret != 0)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		case PANDA_DB_DEL:
			if(m_pCache != NULL || m_pStorage != NULL)
				return remove(data, dlen );
			break;

		case PANDA_SQL_MPL:
		case PANDA_SQL_PQL: 
		case PANDA_SQL_CPL:
		case PANDA_SQL_SQL:
			if(m_pStorage != NULL && dlen >3*sizeof(unsigned)+1)
				iret = m_pStorage->SQL(method, data, dlen, wbuf, wsize, wbytes);
	DEBUG4(" wsize=", wsize,"; wbytes=", wbytes );
			if(iret != 0 || wbytes < 1)
				get_mem_return_info(wbuf, iret , wsize, wbytes);
			break;
		default:
			DEBUG2("not defined method = ", method);
			return PANDA_ERR_INPUT_DATA;
	}
	DEBUG6("wbuf =", *wbuf, "; wsize=", wsize,"; wbytes=", wbytes );
	return iret;
}

